﻿//==============================================================================
// File: GroupingViewsCollection.cs
// Created: 2010-05-28
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Mammoth. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using Microsoft.Practices.Composite.Regions;

namespace WpfRcp.Core.Common
{
    public sealed class GroupingViewsCollection : IEnumerable, INotifyCollectionChanged, IComparer<object>
    {
        private readonly Type m_Separator;
        private readonly List<object> m_Items;

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public GroupingViewsCollection(IEnumerable views, Type separator)
        {
            m_Separator = separator;
            m_Items = new List<object>();

            var notifiableCollection = views as INotifyCollectionChanged;
            if (notifiableCollection != null)
            {
                notifiableCollection.CollectionChanged += OnParentViewsCollectionChanged;
            }

            SynchronizeWithViewsCollection(views);
        }

        private void OnParentViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            SynchronizeWithViewsCollection((IEnumerable)sender);
        }

        private void SynchronizeWithViewsCollection(IEnumerable collection)
        {
            m_Items.Clear();

            foreach (var o in collection)
            {
                m_Items.Add(o);
            }

            //m_Items.Sort(this);

            OnCollectionChanged();
        }

        public IEnumerator GetEnumerator()
        {
            string currentGroup = null;
            bool separatorRequired = false;

            foreach (object o in m_Items)
            {
                var dependencyObject = o as DependencyObject;
                var uiElement = o as UIElement;

                if (dependencyObject != null)
                {
                    string group = GetGroupName(dependencyObject);

                    if (group != currentGroup)
                    {
                        currentGroup = group;

                        if (separatorRequired)
                        {
                            separatorRequired = false;
                            yield return Activator.CreateInstance(m_Separator);
                        }
                    }
                }

                if ((uiElement != null) && (uiElement.Visibility == Visibility.Visible))
                {
                    separatorRequired = true;
                }

                yield return o;
            }
        }

        #region Comparer

        int IComparer<object>.Compare(object x, object y)
        {
            int? xGroupIndex = null;
            int? yGroupIndex = null;
            int? xIndex = null;
            int? yIndex = null;

            var xDependencyObject = x as DependencyObject;
            var yDependencyObject = y as DependencyObject;

            if (xDependencyObject != null)
            {
                xGroupIndex = GetGroupIndex(xDependencyObject);
                xIndex = GetIndex(xDependencyObject);
            }

            if (yDependencyObject != null)
            {
                yGroupIndex = GetGroupIndex(yDependencyObject);
                yIndex = GetIndex(yDependencyObject);
            }

            // There is a little bug in this logic: items with explicit group index int.MinValue will be grouped with items with
            // no explicit group index. Makes the code simpler, though.

            int groupCompare = xGroupIndex.GetValueOrDefault(int.MinValue).CompareTo(yGroupIndex.GetValueOrDefault(int.MinValue));

            if (groupCompare == 0)
            {
                // Same group, so compare indexes.
                return xIndex.GetValueOrDefault(int.MinValue).CompareTo(yIndex.GetValueOrDefault(int.MinValue));
            }

            return groupCompare;
        }

        #endregion

        #region Dependency Properties

        /// <summary>
        /// Identifies the <c>GroupIndex</c> attached property.
        /// </summary>
        public static readonly DependencyProperty GroupIndexProperty =
            DependencyProperty.RegisterAttached("GroupIndex", typeof(int?), typeof(GroupingViewsCollection));

        /// <summary>
        /// Identifies the <c>Index</c> attached property.
        /// </summary>
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.RegisterAttached("Index", typeof(int?), typeof(GroupingViewsCollection));

        /// <summary>
        /// Gets the group index for the specified item.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <returns>
        /// The item's group index, or <see langword="null"/> if it has none.
        /// </returns>
        public static int? GetGroupIndex(DependencyObject item)
        {
            return (int?)item.GetValue(GroupIndexProperty);
        }

        /// <summary>
        /// Sets an item's group index.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <param name="groupIndex">
        /// The group index to apply to the item.
        /// </param>
        public static void SetGroupIndex(DependencyObject item, int? groupIndex)
        {
            item.SetValue(GroupIndexProperty, groupIndex);
        }

        /// <summary>
        /// Gets the index for the specified item.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <returns>
        /// The item's index, or <see langword="null"/> if it has none.
        /// </returns>
        public static int? GetIndex(DependencyObject item)
        {
            return (int?)item.GetValue(IndexProperty);
        }

        /// <summary>
        /// Sets an item's index.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <param name="index">
        /// The group index to apply to the item.
        /// </param>
        public static void SetIndex(DependencyObject item, int? index)
        {
            item.SetValue(IndexProperty, index);
        }

        public static readonly DependencyProperty GroupNameProperty =
            DependencyProperty.RegisterAttached("GroupName", typeof(string), typeof(GroupingViewsCollection));

        public static readonly DependencyProperty GroupTextProperty =
            DependencyProperty.RegisterAttached("GroupText", typeof(string), typeof(GroupingViewsCollection));

        public static readonly DependencyProperty RegionProperty =
            DependencyProperty.RegisterAttached("Region", typeof(IRegion), typeof(GroupingViewsCollection));

        public static string GetGroupName(DependencyObject obj)
        {
            return (string)obj.GetValue(GroupNameProperty);
        }

        public static void SetGroupName(DependencyObject obj, string value)
        {
            obj.SetValue(GroupNameProperty, value);
        }

        public static string GetGroupText(DependencyObject obj)
        {
            return (string)obj.GetValue(GroupTextProperty);
        }

        public static void SetGroupText(DependencyObject obj, string value)
        {
            obj.SetValue(GroupTextProperty, value);
        }

        public static IRegion GetRegion(DependencyObject item)
        {
            return (IRegion)item.GetValue(RegionProperty);
        }

        public static void SetRegion(DependencyObject item, IRegion region)
        {
            item.SetValue(RegionProperty, region);
        }

        #endregion

        private void OnCollectionChanged()
        {
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}